前面我們連續看了兩個對避免畫面重新渲染有幫助的hook,今天緊接著再來看另一個也有助於優化效能,並提升使用者體驗的方式。之前看的幾個hooks,主要都是透過避免元件進行多餘的重新渲染,或避免進行多餘計算的方式來改善頁面的效能,今天的主題「lazy」和「Suspense」則是透過**「拆分程式碼」和「動態載入」,以及「避免畫面渲染阻塞」**的方式來改善效能和使用者體驗。
「lazy」是React用來處理元件「動態載入」的功能,所謂的動態載入,白話一點來說明的話,也就是需要某個JavaScript檔案時,才把這個檔案載入,也就可以避免一開始進入畫面時,花太多時間載入一些當前頁面不需要的JavaScript。而這樣的作法,也可以達到拆分程式碼(Code Splitting)的效果 ,因為每個用lazy處理的元件會被獨立成一個JavaScript檔案,就不再是一大包內含所有元件程式碼的JavaScript檔案。
「Suspense」是React提供來優化使用者體驗的功能,使用Suspense可以在非同步處理,或是動態加載元件時的等待狀態,例如在等待時間先在畫免顯示loading文字,讓使用者感覺網頁有一直在動,而不是停止不動,等到資料載完,再顯示預定要顯示的畫面。雖然「Suspense」常與 「lazy」一起使用以實現程式碼拆分和動態加載,但它也可以單獨用於處理非同步操作的等待。
大概了解suspense和lazy是什麼用途後,接著來看看要怎麼使用lazy和suspense。
當我們要把元件變成lazy loaded的元件的時候,可以這樣做。
// 先import lazy
import { lazy } from 'react';
// 再宣告lazy loaded元件
const ALazyLoadedComponent = lazy(() => import ('./components/AComponent'));
如果想要讓切分出來的程式碼,能有指定的命名的話,還可以用下面的方式命名。
// 先import lazy
import { lazy } from 'react';
// 可以這樣加上webpackChunkName,讓切分出來的程式碼能被標示上指定的的名字
const ALazyLoadedComponent = lazy(() => import (/* webpackChunkName: "AComponent" */'./components/AComponent'));
沒有指定命名時,加載進來的檔名會顯示成這樣。
有指定命名時,則會依照指定的名字顯示。
當我們使用lazy或是進行一些非同步拉取資料的動作時,中間有可能會有一小段等待時間,這時候就可以這樣使用Suspens來處理。
import { lazy, Suspense, useState } from 'react';
const ALazyLoadedComponent = lazy(() => import ('./components/AComponent'));
function App() {
return (
<div className="App">
<Suspense fallback="loading">
<ALazyLoadedComponent />
</Suspense>
</div>
);
}
Suspense不僅適合搭配lazy loaded元件一起使用,也很適合拿來優化打api拉取到資料之前的狀態。以前可能需要自己寫個isLoading的值拿來切換資料回來前和回來後的畫面轉變,使用Suspense,就不需要額外再寫一個值做判斷。因為Suspense會在chrilderen渲染被block住的時候,先渲染fallback的內容,以提供使用者更好的使用體驗。
知道怎麼使用之後,緊接著再來看看有沒有使用lazy和Suspense差異到底在哪裡?
這裡的例子以一個單純的例子來示範:
這個例子的情境是畫面上顯示什麼子元件會依照一個state來決定,所以當這個被拿來判斷要顯示哪個元件的state被更改之前,另一個元件有沒有被載入對畫面都的顯示都不會有影響。
{
isAComponent ? <AComponent /> : <BComponent />
}
尚未使用lazy和Suspense時
在沒有使用lazy搭配Suspen之前,不管是不是當下要使用的元件都會在一進入畫面的時候就被載入。所以如果觀察Dev Tools內的network的話,可以發現載入的bundle.js內含AComponent和BComponent的內容。
使用lazy和Suspense之後
在使用lazy搭配Suspense後,會發現原本的bundle.js沒有直接包含這兩個元件的內容了,取得代之的是多了兩個新的js檔案,而且不會在一開始載入畫面的時候就把兩個js檔案都載入,只會在第一次用到的時候再載入。
這樣能達到的效果也就是前面所提到過的「減少第一次載入畫面時,需要載入的js檔案大小,將js的程式碼拆分,等到需要的時候,再載入」,也因為是以這樣的方式下去載入js檔案,就能加快畫面載入的速度。
雖然前面都一直讓男主角React出現,但我們可不能忘了我們的男二Vue啊!其實Vue也有類似lazy的動態載入元件的方式,不過大多都是搭配vue-router一起做使用,例如以下的這種寫法。
const routes = [
{
path: '/',
name: 'Home',
// 元件lazy load的部分
component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue'),
},
// 其他router的設定...
];
如果單獨使用的話,則可以透過defineAsyncComponent來定義lazy loaded元件。
import { defineAsyncComponent } from 'vue'
const LazyLoadedComponent = defineAsyncComponent(() =>
import('./components/AComponent.vue')
)
除了lazy的功能外,Vue也有Suspense的功能,不過這個功能還在實驗性質,所以可能還不太穩定,可以稍微參考一下就好。在用法上跟React很類似,一樣是將Suspense包在最外圍,但是裡面的內容是用template區分不同時機點顯示的內容。
<template>
<div>
<!-- 當非同步的動作處理完後,就會顯示default的內容 -->
<Suspense>
<template #default>
<AsyncComponent />
</template>
<!-- 如果非同步動作還沒進行完畢,則會顯示fallback的內容 -->
<template #fallback>
<Loading />
</template>
</Suspense>
</div>
</template>
今天又看了一種與效能優化有關的作法,有別於之前提到「避免多餘的重新渲染」以及「避免多餘的重新計算」,今天看的lazy和suspense主要是用於將js檔案拆小,並以動態載入的方式,避免初次進入畫面,就載入所有當前頁面不需要的程式碼,並透過suspense讓畫面渲染出現阻塞狀況時,先載入一個加載中的畫面,以提升使用者體驗。雖然lazy和suspense的功能在React和Vue中都有,但是使用上稍微有些差異,且Suspense的功能在Vue中還在實驗階段,所以使用時,可能要多留意一下這部分。